using System;
using System.Collections.Generic;
using System.Text;
using VisualizationPackage.FileLoader;
using System.Collections.ObjectModel;
using System.Drawing;

namespace VisualizationPackage.VectorField
{
    public class StreamLine
    {
        private int _uVector;
        public int UVector
        {
            get { return _uVector; }
            set { _uVector = value; }
        }
        private int _vVector;
        public int VVector
        {
            get { return _vVector; }
            set { _vVector = value; }
        }
        private int _wVector;
        public int WVector
        {
            get { return _wVector; }
            set { _wVector = value; }
        }
        private double _deltaT;
        public double DeltaT
        {
            get { return _deltaT; }
            set { _deltaT = value; }
        }
        private Vertex _start;
        public Vertex Start
        {
            get { return _start; }
            set { _start = value; }
        }
        private List<Line> _path;
        public ReadOnlyCollection<Line> Path
        {
            get
            {
                if (_path != null)
                    return _path.AsReadOnly();
                return null;
            }
        }
        private Visualization.Zone _workingZone;
        public Visualization.Zone WorkingZone
        {
            get { return _workingZone; }
        }
        private object _parent;

        public object Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        public static List<StreamLine> StreamLines = new List<StreamLine>();

        public StreamLine()
        {
        }
        public StreamLine(Vertex Start, int U, int V, int W, double deltaT, Visualization.Mesh Mesh)
        {
            this.Start = Start;
            UVector = U;
            VVector = V;
            WVector = W;
            DeltaT = deltaT;
            foreach (Visualization.Zone zone in Mesh.Zones)
            {
                if (FindElement(zone, Start) != null)
                    _workingZone = zone;
            }
        }
        public StreamLine(Vertex Start, int U, int V, int W, double deltaT, Visualization.Zone zone)
        {
            this.Start = Start;
            UVector = U;
            VVector = V;
            WVector = W;
            DeltaT = deltaT;
            _workingZone = zone;
        }
        public static Visualization.Element FindElement(Visualization.Zone zone, Vertex vertex)
        {
            foreach (Visualization.Element element in zone.Elements)
                if (IsInside(vertex, element, zone))
                    return element;
            return null;
        }
        public static Visualization.Element FindElement(Visualization.Zone zone, Vertex vertex, Visualization.Element StartElement)
        {
            //return FindElement(zone, vertex);
            //Fan Search, use the startElement as your base, then check all other elements that shares any
            // vertex with you.

            Queue<uint> Elements = new Queue<uint>();
            SortedDictionary<uint, uint> visitedElements = new SortedDictionary<uint, uint>();
            foreach (uint face in StartElement.Faces)
                foreach (uint verInd in zone.Faces[face].Vertices)
                    for (int i = 0; i < zone.Vertices[verInd].last; i++)
                        Elements.Enqueue(zone.Vertices[verInd].Elements[i]);
            while (Elements.Count > 0)
            {
                uint element = Elements.Dequeue();
                if (visitedElements.ContainsKey(element))
                    continue;
                if (IsInside(vertex, zone.Elements[element], zone))
                    return zone.Elements[element];
                visitedElements[element] = element;
                foreach (uint face in zone.Elements[element].Faces)
                    foreach (uint verInd in zone.Faces[face].Vertices)
                        for (int i = 0; i < zone.Vertices[verInd].last; i++)
                            Elements.Enqueue(zone.Vertices[verInd].Elements[i]);
            }
            return null;
        }

        private static bool IsInside(Vertex vertex, Visualization.Element element, Visualization.Zone zone)
        {
            // Checks wheather a given vertex is inside (or on the bounds of) a given element..
            // it projects a horizontal ray and counts how many faces does it hit... odd number means inside..
            Vertex v = vertex.Copy();
            v.X = 0;
            v.Y = 1;
            v.Z = 0;

            int count = 0;

            for (int i = 0; i < element.Faces.Length; i++)
            {
                Visualization.Face face = zone.Faces[element.Faces[i]];
                Vertex v0 = new Vertex(zone.Vertices[face.Vertices[0]]);
                Vertex v1 = new Vertex(zone.Vertices[face.Vertices[1]]);
                Vertex v2 = new Vertex(zone.Vertices[face.Vertices[2]]);
                Vertex Normal = Util.Normal(v0, v1, v2);

                float d = -1 * (float)v0.DotMultiplyPositionVector(Normal);
                float NO = (float)Normal.DotMultiplyPositionVector(vertex);
                float ND = (float)Normal.DotMultiplyPositionVector(v);
                float t = -1 * (d + NO) / ND;
                Vertex res = vertex + v * t;

                if (Math.Abs(ND) >= 0.0000001 && t >= 0 && IsInside(res, face, zone))
                    count++;
            }
            return count % 2 != 0;
        }
        public static bool IsInside(Vertex q, Visualization.Face face, Visualization.Zone zone)
        {
            // Checks whether a given vertex q is inside a polygon face or not... it sums the angles between the vertex and 
            // all other edges, if this equals 360 (2 PI) then the polygon is in the face.
            int i, n = face.Vertices.Length;
            double m1, m2;
            double anglesum = 0, costheta;
            Visualization.Vertex p1 = new Visualization.Vertex(), p2 = new Visualization.Vertex();

            for (i = 0; i < n; i++)
            {
                Visualization.Vertex v1 = zone.Vertices[face.Vertices[i]];
                Visualization.Vertex v2 = zone.Vertices[face.Vertices[(i + 1) % n]];

                Visualization.Vector3 vec1 = v1.Position - new Visualization.Point3(q.X, q.Y, q.Z);
                p1.Position = new Visualization.Point3(vec1.x, vec1.y, vec1.z);
                Visualization.Vector3 vec2 = v2.Position - new Visualization.Point3(q.X, q.Y, q.Z);
                p2.Position = new Visualization.Point3(vec2.x, vec2.y, vec2.z);

                m1 = Util.Modulus(p1);
                m2 = Util.Modulus(p2);
                if (m1 * m2 <= 0.0000001)
                    return true; /* We are on a node, consider this inside */
                else
                    costheta = (p1.Position.x * p2.Position.x + p1.Position.y * p2.Position.y + p1.Position.z * p2.Position.z) / (m1 * m2);
                anglesum += Math.Acos(costheta);
            }
            return Math.Abs(anglesum - Math.PI * 2) <= 0.000001;
        }
        public ReadOnlyCollection<Line> Calculate()
        {
            if (Path != null)
                return Path;
            Vertex Start = this.Start.Copy();
            List<Line> lines = new List<Line>();
            List<Vertex> verts = new List<Vertex>();

            Visualization.Element element = WorkingZone.Elements[0];

            Vertex vertex = Start.Copy();
            int counterDummy = 0;
            while ((element = FindElement(WorkingZone, vertex, element)) != null)
            {
                counterDummy++;
                vertex = Util.InterpolateVector(element, vertex, WorkingZone, GlobalVariables.Data[UVector], GlobalVariables.Data[VVector], GlobalVariables.Data[WVector]);
                vertex = Animate(WorkingZone, element, vertex, DeltaT);
                if (vertex == null)
                    return (_path = lines).AsReadOnly();

                lines.Add(new Line(Start, vertex));
                verts.Add(vertex);
                Start = vertex.Copy();
                vertex = vertex.Copy();
            }
            return (_path = CalculateBezier(lines)).AsReadOnly();// CreateLines(VisualizationPackage.Bezier.CalculateBezier(verts.ToArray(), verts.Count * 5))).AsReadOnly();
        }

        private List<Line> CreateLines(Vertex[] vertices)
        {
            List<Line> lines = new List<Line>();
            for (int i = 0; i < vertices.Length - 1; i++)
                lines.Add(new Line(vertices[i], vertices[i + 1]));
            return lines;
        }

        private List<Line> CalculateBezier(List<Line> lines)
        {
            List<Line> res = new List<Line>();
            for (int i = 0; i < lines.Count - 4; i += 3)
            {
                Vertex p0 = lines[i].Vertex1;
                Vertex p1 = lines[i + 1].Vertex1;
                Vertex p2 = lines[i + 2].Vertex1;
                Vertex p3 = lines[i + 3].Vertex1;
                for (float f = 0.0f; f <= 1; f += 0.01f)
                {
                    Vertex pRes1 = Util.CubicBezier(f, p0, p1, p2, p3);
                    Vertex pRes2 = Util.CubicBezier(f + 0.01f, p0, p1, p2, p3);
                    res.Add(new Line(pRes1, pRes2));
                }
            }
            return res;
        }

        private Vertex Animate(Visualization.Zone zone, Visualization.Element element, Vertex vertex, double DeltaT)
        {
            //vertex.X += vertex[GlobalVariables.Data[UVector]] * DeltaT;
            //vertex.Y += vertex[GlobalVariables.Data[VVector]] * DeltaT;
            //vertex.Z += vertex[GlobalVariables.Data[WVector]] * DeltaT;
            //return vertex;

            Vertex dummyV = new Vertex();
            dummyV.X = vertex[GlobalVariables.Data[UVector]];
            dummyV.Y = vertex[GlobalVariables.Data[VVector]];
            dummyV.Z = vertex[GlobalVariables.Data[WVector]];
            
            dummyV.X /= dummyV.Magnitude();
            dummyV.Y /= dummyV.Magnitude();
            dummyV.Z /= dummyV.Magnitude();

            Vertex k1 = dummyV * DeltaT;
            Vertex k2Temp = vertex + k1;
            Vertex k2 = new Vertex();
            Visualization.Element tempElement = FindElement(zone, vertex, element);
            if (tempElement == null)
                return null;
            k2 = Util.InterpolateVector(element, k2Temp, zone, GlobalVariables.Data[UVector], GlobalVariables.Data[VVector], GlobalVariables.Data[WVector]);
            //k2 = Util.Interpolate(tempElement, k2Temp, zone, GlobalVariables.Data[UVector]);
            //k2 = Util.Interpolate(tempElement, k2Temp, zone, GlobalVariables.Data[VVector]);
            //k2 = Util.Interpolate(tempElement, k2Temp, zone, GlobalVariables.Data[WVector]);

            dummyV.X = k2[GlobalVariables.Data[UVector]];
            dummyV.Y = k2[GlobalVariables.Data[VVector]];
            dummyV.Z = k2[GlobalVariables.Data[WVector]];
            //dummyV.X /= dummyV.Magnitude();
            //dummyV.Y /= dummyV.Magnitude();
            //dummyV.Z /= dummyV.Magnitude();

            dummyV = dummyV * DeltaT;

            return vertex + (k1 + dummyV) / 2;

            //vertex.X += vertex[GlobalVariables.Data[UVector]] * DeltaT;
            //vertex.Y += vertex[GlobalVariables.Data[VVector]] * DeltaT;
            //vertex.Z += vertex[GlobalVariables.Data[WVector]] * DeltaT;
        }

        public override string ToString()
        {
            return "X:" + Math.Round(Start.X, 3) + ", Y:" + Math.Round(Start.Y, 3) + ", Z:" + Math.Round(Start.Z, 3);
            return "U " + GlobalVariables.Data[UVector] + ", V " + GlobalVariables.Data[VVector] + ", W " + GlobalVariables.Data[WVector];
        }
    }
}
